﻿// *----------------------------------------------------------------
// Copyright (C) 2017 通通优品
// 版权所有。
// 
// 类名：SettingManager.cs
// 功能描述：TongTongMall
// 
// Create User：jym 2017/02/09
// 
// Edit User：UserName,EditTime
// Describe：Describe
// ----------------------------------------------------------------*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using TongTongMall.Dependecy;
using TongTongMall.Domain.Uow;
using TongTongMall.Runtime.Caching;
using TongTongMall.Runtime.Session;
using TongTongMall.Tenant;
using TongTongMall.Collections.Extensions;
using System.Collections.Immutable;

namespace TongTongMall.Configuration
{
    public class SettingManager : ISettingManager, ISingletonDependency
    {
        public const string ApplicationSettingsCacheKey = "ApplicationSettings";

        /// <summary>
        /// 当前Session信息
        /// </summary>
        public ITongTongMallSession Session { get; set; } = NullSession.Instance;

        /// <summary>
        /// 实现配置数据持久化的操作类.
        /// </summary>
        public ISettingStore SettingStore { get; set; } = DefaultConfigSettingStore.Instance;

        private readonly ISettingDefinitionManager _settingDefinitionManager;
        private readonly ITypedCache<string, Dictionary<string, SettingInfo>> _applicationSettingCache;
        private readonly ITypedCache<string, Dictionary<string, SettingInfo>> _tenantSettingCache;
        private readonly ITypedCache<string, Dictionary<string, SettingInfo>> _userSettingCache;


        public SettingManager(
            ISettingDefinitionManager settingDefinitionManager, 
            ICacheManager cacheManager)
        {
            _settingDefinitionManager = settingDefinitionManager;
            _applicationSettingCache = cacheManager.GetApplicationSettingsCache();
            _tenantSettingCache = cacheManager.GetTenantSettingsCache();
            _userSettingCache = cacheManager.GetUserSettingsCache();

        }

        #region Public methods


        public Task<string> GetSettingValueAsync(string name)
        {
            return GetSettingValueInternalAsync(name, Session.TenantID, Session.UserID);
        }

        public Task<string> GetSettingValueForApplicationAsync(string name)
        {
            return GetSettingValueInternalAsync(name);
        }

        public Task<string> GetSettingValueForTenantAsync(string name, long tenantId)
        {
            return GetSettingValueInternalAsync(name, tenantId);
        }

        public Task<string> GetSettingValueForUserAsync(string name, long tenantId, long userId)
        {
            return GetSettingValueInternalAsync(name, tenantId, userId);
        }

        public async Task<IReadOnlyList<ISettingValue>> GetAllSettingValuesAsync()
        {
            return await GetAllSettingValuesAsync(SettingScopes.Application | SettingScopes.Tenant | SettingScopes.User);
        }


        public async Task<IReadOnlyList<ISettingValue>> GetAllSettingValuesAsync(SettingScopes scopes)
        {
            var settingDefinitions = new Dictionary<string, SettingDefinition>();
            var settingValues = new Dictionary<string, ISettingValue>();

            //填充默认值
            foreach (var setting in _settingDefinitionManager.GetAllSettingDefinitions())
            {
                settingDefinitions[setting.Name] = setting;
                settingValues[setting.Name] = new SettingValueObject(setting.Name, setting.DefaultValue);
            }

            //重写全局配置项
            if (scopes.HasFlag(SettingScopes.Application))
            {
                foreach (var settingValue in await GetAllSettingValuesForApplicationAsync())
                {
                    var setting = settingDefinitions.GetOrDefault(settingValue.Name);


                    if (setting == null || !setting.Scopes.HasFlag(SettingScopes.Application))
                    {
                        continue;
                    }

                    if ((setting.Scopes.HasFlag(SettingScopes.Tenant) && Session.TenantID.HasValue) || (setting.Scopes.HasFlag(SettingScopes.User) && Session.UserID.HasValue))
                    {
                        continue;
                    }

                    settingValues[settingValue.Name] = new SettingValueObject(settingValue.Name, settingValue.Value);
                }
            }

            //重写租户配置信息
            if (scopes.HasFlag(SettingScopes.Tenant) && Session.TenantID.HasValue)
            {
                foreach (var settingValue in await GetAllSettingValuesForTenantAsync(Session.TenantID.Value))
                {
                    var setting = settingDefinitions.GetOrDefault(settingValue.Name);
                    
                    if (setting == null || !setting.Scopes.HasFlag(SettingScopes.Tenant))
                    {
                        continue;
                    }

                    if (setting.Scopes.HasFlag(SettingScopes.User) && Session.UserID.HasValue)
                    {
                        continue;
                    }

                    settingValues[settingValue.Name] = new SettingValueObject(settingValue.Name, settingValue.Value);
                }
            }

            //重写用户配置信息
            if (scopes.HasFlag(SettingScopes.User) && Session.UserID.HasValue)
            {
                foreach (var settingValue in await GetAllSettingValuesForUserAsync(new UserIdentifier(Session.TenantID, Session.UserID)))
                {
                    var setting = settingDefinitions.GetOrDefault(settingValue.Name);
                    if (setting != null && setting.Scopes.HasFlag(SettingScopes.User))
                    {
                        settingValues[settingValue.Name] = new SettingValueObject(settingValue.Name, settingValue.Value);
                    }
                }
            }

            return settingValues.Values.ToImmutableList();
        }
        
        public async Task<IReadOnlyList<ISettingValue>> GetAllSettingValuesForApplicationAsync()
        {
            return (await GetApplicationSettingsAsync()).Values
                .Select(setting => new SettingValueObject(setting.Name, setting.Value))
                .ToImmutableList();
        }
        
        public async Task<IReadOnlyList<ISettingValue>> GetAllSettingValuesForTenantAsync(long tenantId)
        {
            return (await GetReadOnlyTenantSettings(tenantId)).Values
                .Select(setting => new SettingValueObject(setting.Name, setting.Value))
                .ToImmutableList();
        }
        
        public Task<IReadOnlyList<ISettingValue>> GetAllSettingValuesForUserAsync(long userId)
        {
            return GetAllSettingValuesForUserAsync(new UserIdentifier(Session.TenantID, userId));
        }

        public async Task<IReadOnlyList<ISettingValue>> GetAllSettingValuesForUserAsync(UserIdentifier user)
        {
            return (await GetReadOnlyUserSettings(user)).Values
                .Select(setting => new SettingValueObject(setting.Name, setting.Value))
                .ToImmutableList();
        }
        
        [UnitOfWork]
        public virtual async Task ChangeSettingForApplicationAsync(string name, string value)
        {
            await InsertOrUpdateOrDeleteSettingValueAsync(name, value, null, null);
            await _applicationSettingCache.RemoveAsync(ApplicationSettingsCacheKey);
        }


        [UnitOfWork]
        public virtual async Task ChangeSettingForTenantAsync(long tenantId, string name, string value)
        {
            await InsertOrUpdateOrDeleteSettingValueAsync(name, value, tenantId, null);
            await _tenantSettingCache.RemoveAsync($"{tenantId}");
        }


        [UnitOfWork]
        public virtual Task ChangeSettingForUserAsync(long userId, string name, string value)
        {
            return ChangeSettingForUserAsync(new UserIdentifier(Session.TenantID, userId), name, value);
        }

        public async Task ChangeSettingForUserAsync(UserIdentifier user, string name, string value)
        {
            await InsertOrUpdateOrDeleteSettingValueAsync(name, value, user.TenantID, user.UserID);
            await _userSettingCache.RemoveAsync(user.ToUserIdentifierString());
        }

        #endregion

        #region Private methods

        private async Task<string> GetSettingValueInternalAsync(string name, long? tenantId = null, long? userId = null)
        {
            var settingDefinition = _settingDefinitionManager.GetSettingDefinition(name);

            if (settingDefinition.Scopes.HasFlag(SettingScopes.User) && userId.HasValue)
            {
                var settingValue = await GetSettingValueForUserOrNullAsync(new UserIdentifier(tenantId, userId.Value), name);
                if (settingValue != null)
                {
                    return settingValue.Value;
                }
            }

            if (settingDefinition.Scopes.HasFlag(SettingScopes.Tenant) && tenantId.HasValue)
            {
                var settingValue = await GetSettingValueForTenantOrNullAsync(tenantId.Value, name);
                if (settingValue != null)
                {
                    return settingValue.Value;
                }

            }

            if (settingDefinition.Scopes.HasFlag(SettingScopes.Application))
            {
                var settingValue = await GetSettingValueForApplicationOrNullAsync(name);
                if (settingValue != null)
                {
                    return settingValue.Value;
                }
            }

            return settingDefinition.DefaultValue;
        }

        private async Task<SettingInfo> InsertOrUpdateOrDeleteSettingValueAsync(string name, string value, long? tenantId, long? userId)
        {
            var settingDefinition = _settingDefinitionManager.GetSettingDefinition(name);
            var settingValue = await SettingStore.GetSettingOrNullAsync(tenantId, userId, name);

            var defaultValue = settingDefinition.DefaultValue;


            if (tenantId.HasValue || userId.HasValue)
            {
                var applicationValue = await GetSettingValueForApplicationOrNullAsync(name);
                if (applicationValue != null)
                {
                    defaultValue = applicationValue.Value;
                }
                
                if (userId.HasValue && tenantId.HasValue)
                {
                    var tenantValue = await GetSettingValueForTenantOrNullAsync(tenantId.Value, name);
                    if (tenantValue != null)
                    {
                        defaultValue = tenantValue.Value;
                    }
                }
            }
            
            if (value == defaultValue)
            {
                if (settingValue != null)
                {
                    await SettingStore.DeleteAsync(settingValue);
                }

                return null;
            }
            
            if (settingValue == null)
            {
                settingValue = new SettingInfo
                {
                    TenantID = tenantId,
                    UserID = userId,
                    Name = name,
                    Value = value
                };

                await SettingStore.CreateAsync(settingValue);
                return settingValue;
            }
            
            if (settingValue.Value == value)
            {
                return settingValue;
            }
            
            settingValue.Value = value;
            await SettingStore.UpdateAsync(settingValue);

            return settingValue;
        }

        private async Task<SettingInfo> GetSettingValueForApplicationOrNullAsync(string name)
        {
            return (await GetApplicationSettingsAsync()).GetOrDefault(name);
        }

        private async Task<SettingInfo> GetSettingValueForTenantOrNullAsync(long tenantId, string name)
        {
            return (await GetReadOnlyTenantSettings(tenantId)).GetOrDefault(name);
        }

        private async Task<SettingInfo> GetSettingValueForUserOrNullAsync(UserIdentifier user, string name)
        {
            return (await GetReadOnlyUserSettings(user)).GetOrDefault(name);
        }

        private async Task<Dictionary<string, SettingInfo>> GetApplicationSettingsAsync()
        {
            return await _applicationSettingCache.GetAsync(ApplicationSettingsCacheKey, async () =>
            {
                var dictionary = new Dictionary<string, SettingInfo>();

                var settingValues = await SettingStore.GetAllListAsync(null, null);
                foreach (var settingValue in settingValues)
                {
                    dictionary[settingValue.Name] = settingValue;
                }

                return dictionary;
            });
        }

        private async Task<ImmutableDictionary<string, SettingInfo>> GetReadOnlyTenantSettings(long tenantId)
        {
            var cachedDictionary = await GetTenantSettingsFromCache(tenantId);
            lock (cachedDictionary)
            {
                return cachedDictionary.ToImmutableDictionary();
            }
        }

        private async Task<ImmutableDictionary<string, SettingInfo>> GetReadOnlyUserSettings(UserIdentifier user)
        {
            var cachedDictionary = await GetUserSettingsFromCache(user);
            lock (cachedDictionary)
            {
                return cachedDictionary.ToImmutableDictionary();
            }
        }

        private async Task<Dictionary<string, SettingInfo>> GetTenantSettingsFromCache(long tenantId)
        {
            return await _tenantSettingCache.GetAsync(
                $"TenantSysConfig:{tenantId}",
                async () =>
                {
                    var dictionary = new Dictionary<string, SettingInfo>();

                    var settingValues = await SettingStore.GetAllListAsync(tenantId, null);
                    foreach (var settingValue in settingValues)
                    {
                        dictionary[settingValue.Name] = settingValue;
                    }

                    return dictionary;
                });
        }

        private async Task<Dictionary<string, SettingInfo>> GetUserSettingsFromCache(UserIdentifier user)
        {
            return await _userSettingCache.GetAsync(
               $"TenantSysConfigUser:{user.ToUserIdentifierString()}",
                async () =>
                {
                    var dictionary = new Dictionary<string, SettingInfo>();

                    var settingValues = await SettingStore.GetAllListAsync(user.TenantID, user.UserID);
                    foreach (var settingValue in settingValues)
                    {
                        dictionary[settingValue.Name] = settingValue;
                    }

                    return dictionary;
                });
        }

        #endregion

        #region Nested classes

        private class SettingValueObject : ISettingValue
        {
            public string Name { get; private set; }

            public string Value { get; private set; }

            public SettingValueObject(string name, string value)
            {
                Value = value;
                Name = name;
            }
        }

        #endregion
    }
}
